package main

import (
	"fmt"
	"net"
	"strings"
	"sync"
	"time"
)

// User 定义user
type User struct {
	//唯一ID
	id   string
	name string
	//管道
	msg chan string
}

//创建全局map，用于保存所有的用户
//map不允许同时读写，如果有不同的go程操作map，需要对map上锁
var allUsers = make(map[string]User)
var lock sync.RWMutex

//定义一个message全局通道，用于接收任何人发送过来的消息
var message = make(chan string, 10)

func main() {
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("net.Listen err:", err)
		return
	}

	fmt.Println("server start success, listening....")

	//启动全局唯一的go程，负责监听message通道
	go broadCast()

	for {
		fmt.Println("主go程监听中....")
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err: ", err)
			return
		}
		fmt.Println("建立连接成功!")

		//启动处理业务的go程
		go handler(conn)
	}

}

func handler(conn net.Conn) {

	fmt.Println("启动业务....")

	//客户端和服务端建立链接的时候，会有IP和port ==> 当成user的id
	clientAddr := conn.RemoteAddr().String()
	fmt.Println("clientAddr: ", clientAddr)
	//创建user
	newUser := User{
		id:   clientAddr,            //id不会修改，作为map的key
		name: clientAddr,            //可以修改，会提供rename命令修改，建立连接时，初始值和ID相同
		msg:  make(chan string, 10), //注意要make空间，否则无法写入数据
	}

	lock.Lock()
	//添加user到map结构
	allUsers[newUser.id] = newUser
	lock.Unlock()

	// 定义一个退出信号，用于监听client退出
	var isQuit = make(chan bool)

	//创建一个用于重置计数器的管道，用于告知watch函数，当前用户正在输入
	var restTimer = make(chan bool)

	// 启动一个go程，负责监听退出信号
	go watch(&newUser, conn, isQuit, restTimer)

	//启动go程，负责将message信息返回给客户端
	go writeBackToClient(&newUser, conn)

	//向message写入数据，当前用户上线的消息，用于通知所有人
	loginInfo := fmt.Sprintf("[%s]:[%s] ===> 上线了login!\n", newUser.id, newUser.name)
	message <- loginInfo
	//fmt.Println

	for {
		//具体业务逻辑
		buf := make([]byte, 1024)
		//读取客户端发送过来的数据
		cnt, err := conn.Read(buf)

		if cnt == 0 { //ctrl+c ====> cnt == 0
			fmt.Println("客户端主动关闭ctrl+c，准备退出!")
			//发出一个退出信号，统一做退出处理，可以使用新的管道来做信号传递
			isQuit <- true
		}

		if err != nil {
			fmt.Println("conn.Read err:", err, ", cnt: ", cnt)
			return
		}

		fmt.Println("服务器接收客户端发送过来的数据为: ", string(buf[:cnt-1]), "cnt: ", cnt)
		//-----业务逻辑处理--------
		// 1 查询当前所有的用户 who
		userInput := string(buf[:cnt-1]) //这是用户输入的数据，最后一个是回车，我们去掉它
		// a. 先判断接收的数据是不是who
		if len(userInput) == 4 && userInput == "\\who" {
			// b. 遍历allUsers这个map， 将id和name拼接成一个字符串，返回客户端
			fmt.Println("用户即将查询所有用户信息！")

			//这个切片包含所有的用户信息
			var userInfos []string

			lock.Lock()
			for _, user := range allUsers {
				userInfo := fmt.Sprintf("userId:%s, username:%s", user.id, user.name)
				userInfos = append(userInfos, userInfo)
			}
			lock.Unlock()

			//最终写到管道中，一定是一个字符串
			res := strings.Join(userInfos, "\n")

			//将数据返回给查询的客户
			newUser.msg <- res

		} else if len(userInput) > 9 && userInput[:7] == "\\rename" { // \rename
			//func Split(s, sep string) []string
			//array := strings.Split(userInput, "|")
			//name := array[1]
			newUser.name = strings.Split(userInput, "|")[1]
			lock.Lock()
			allUsers[newUser.id] = newUser //更新map中user
			lock.Unlock()
			newUser.msg <- "rename successfully!"
		} else {
			//如果用户输入的不是命令，只是普通的聊天信息，那么只需要写到广播通道中即可，由其它的go程进行处理
			message <- string(buf[0:cnt])

		}

		restTimer <- true

	}
	//TODO 以后再具体实现
}

//向所有用户广播消息，启动一个全局唯一go程
func broadCast() {
	fmt.Println("广播go程启动成功....")
	defer fmt.Println("broadCast 程序退出!")

	for {
		//1. 从message中读取数据
		fmt.Println("broadcast 监听message中....")
		info := <-message
		fmt.Println("message 接收到消息:", info)
		//2. 将数据写入到每一个用户的msg管道中
		lock.Lock()
		for _, user := range allUsers {
			//如果msg是非缓冲的，那么会在这里阻塞
			user.msg <- info
		}
		lock.Unlock()
	}

}

//  每个用户应该还有一个用来监听自己msg管道的go程，负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn) {
	fmt.Printf("user: %s 的go程正在监听自己的msg管道\n", user.name)

	for data := range user.msg {
		fmt.Printf("user: %s 写回客户端的数据:%s\n\n", user.name, data)
		_, _ = conn.Write([]byte(data))
	}
}

//启动一个go程，负责监听退出信号,触发后进行资源清理
func watch(user *User, conn net.Conn, isQuit, restTimer <-chan bool) {
	fmt.Println("启动监听退出信号的go程...")
	defer fmt.Println("watch go程退出!")

	for {
		select {
		case <-isQuit:
			logoutInfo := fmt.Sprintf("%s exit already!", user.name)
			fmt.Println("删除当前用户: ", user.name)
			lock.Lock()
			delete(allUsers, user.id) //删除map
			lock.Unlock()
			message <- logoutInfo
			conn.Close()
			return
		case <-time.After(10 * time.Second):
			logoutInfo := fmt.Sprintf("%s timeout exit already!", user.name)
			fmt.Println("删除当前用户: ", user.name)
			lock.Lock()
			delete(allUsers, user.id) //删除map
			lock.Unlock()
			message <- logoutInfo
			conn.Close()
			return

		case <-restTimer:
			fmt.Printf("连接%s ,重置计数器!\n", user.name)
		}
	}
}
